package producersConsumersTests.bypassersCount;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import prod_cons.TaskContainer;
import producersConsumersTests.results.EventSupervisor;
import producersConsumersTests.utils.Test;
import producersConsumersTests.utils.TesterVisitor;

public class BypassCountTestV2 implements Test {
	// Tree container parameters.
	public static final int treeHeight;
	public static final int treeSize;
	static {
		String heightStr = System.getProperty("initialHeight");
		if (heightStr != null)
			treeHeight = Integer.parseInt(heightStr);
		else
			treeHeight = 20;
		treeSize = (int) Math.pow(2, treeHeight) - 1;
	}

	// inserter class
	private static class BypassTestInserter implements Runnable {
		private Semaphore sem;
		private TaskContainer container;
		private final int id, insertersNum, removersNum;

		public BypassTestInserter(Semaphore sem, TaskContainer container,
				int id, int insertersNum, int removersNum) {
			this.sem = sem;
			this.container = container;
			this.id = id;
			this.insertersNum = insertersNum;
			this.removersNum = removersNum;
		}

		@Override
		public void run() {
			// insert N tasks, just as in BypassCountTestV1
			for (int i = 0; i < treeSize; i++) {
				ClockVectorTask t = new ClockVectorTask(id, insertersNum,
						removersNum);
				t.updateEntry(id);
				container.put(t);
				Schedule.get().incrementEntranceEntry(id, id);
			}
			Schedule.get().updateEntryRow(id, id);
			// Inform about finish
			System.out.println(sem.availablePermits()+"i ");
			sem.release();
		}
	}

	private static class BypassTestRemover implements Runnable {
		private Semaphore sem;
		private TaskContainer container;
		private final int id;
		private int removalNum = 0;
		private final int updateParam;
		// List of tasks and their timings
		private List<ClockVectorTask> measures = new LinkedList<ClockVectorTask>();
		// Temporary vector clock array
		private long[] exitVectorClock;

		private ClockVectorTask attemptRemoval() {
			for (int j = 0; j < exitVectorClock.length; j++) {
				exitVectorClock[j] = Schedule.get().getExit(j, id);
			}
			ClockVectorTask task = (ClockVectorTask) container.get();
			if (task != null) {
				Schedule.get().incrementExitEntry(id, id);
				if (++removalNum == updateParam) {
					Schedule.get().updateExitRow(id, id);
					removalNum = 0;
				}
				measures.add(task);
				task.setExit(exitVectorClock);
				task.setRemoverId(id);
			}
			return task;
		}

		public BypassTestRemover(TaskContainer container, int id,
				int updateParam, int removersNum, Semaphore sem) {
			this.container = container;
			this.id = id;
			this.updateParam = updateParam;
			exitVectorClock = new long[removersNum];
			this.sem = sem;
		}

		@Override
		public void run() {
			int i = 0;
			ClockVectorTask task = null;
			// N removal attempts
			while (i < treeSize) {
				task = attemptRemoval();
				if (task != null)
					i++;
			}
			System.out.println(sem.availablePermits()+"r ");
			sem.release();
		}

		public List<ClockVectorTask> getMeasures() {
			return measures;
		}
	}

	private List<EventSupervisor> supervisors = new LinkedList<EventSupervisor>();

	// EventSupervisors are saved so that we may save the data in the CSV
	public List<EventSupervisor> getSupervisors() {
		return supervisors;
	}

	// Analyze each run separately
	private void processTestsData(Collection<List<ClockVectorTask>> measures) {
		for (List<ClockVectorTask> m : measures) {
			EventSupervisor supervisor = new EventSupervisor(m);
			try {
				System.out.println("Average bypassers: "
						+ supervisor.calculateAverageBypassers());
				supervisors.add(supervisor);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	// I credit DimaP for teaching me Visitor design pattern :)
	@Override
	public void accept(TesterVisitor v) {
		v.visit(this);
	}

	@Override
	public void test(int insertersNum, int removersNum, int peakLength,
			int timeBetweenPeaks, int timeToRun, TaskContainer container,
			int extraInt) throws InterruptedException, InstantiationException,
			IllegalAccessException, ClassNotFoundException {
		ExecutorService exec;

		// Set number of threads:
		if (insertersNum + removersNum > 32)
			exec = Executors.newFixedThreadPool(32);
		else
			exec = Executors.newFixedThreadPool(insertersNum + removersNum);
		// initialize schedule
		Schedule.init(insertersNum, removersNum);
		// A collection for all measures
		Collection<List<ClockVectorTask>> allMeasures = new LinkedList<List<ClockVectorTask>>();
		// On each run:
		for (int i = 0; i < timeToRun; ++i) {
			// Create a task list, a semaphore, inserters and removers for it
			List<ClockVectorTask> measures = new LinkedList<ClockVectorTask>();
			Semaphore sem = new Semaphore(-insertersNum - removersNum + 1);
			Runnable inserters[] = new BypassTestInserter[insertersNum];
			BypassTestRemover removers[] = new BypassTestRemover[removersNum];
			// initialize runnables
			for (int j = 0; j < insertersNum; ++j) {
				inserters[j] = new BypassTestInserter(sem, container, j,
						insertersNum, removersNum);
			}
			for (int j = 0; j < removersNum; j++) {
				removers[j] = new BypassTestRemover(container, j, extraInt,
						removersNum, sem);
			}
			// start here tests all together
			for (Runnable ins : inserters) {
				exec.execute(ins);
			}
			for (BypassTestRemover rem : removers) {
				exec.execute(rem);
			}
			// wait for all threads to finish
			sem.acquire();
			// get measures and store them.
			for (BypassTestRemover r : removers) {
				measures.addAll(r.getMeasures());
			}
			allMeasures.add(measures);
			System.out.println("run " + i + " completed");
		}
		exec.shutdown();
		// The deadline is 1 week away! :)
		exec.awaitTermination(7, TimeUnit.DAYS);
		System.out.println("Test complete! Found " + allMeasures.size()
				+ " runs.");
		processTestsData(allMeasures);
	}

	@Override
	public void test(int insertersNum, int removersNum, double lambda,
			int timeToRun, TaskContainer container, int extraInt)
			throws InterruptedException, InstantiationException,
			IllegalAccessException, ClassNotFoundException {
		System.out.println("No Poisson run for this test. Sorry!");
	}

}
